/*******************************************************************************
 * Copyright (c) 2007, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ceylon.ide.eclipse.code.refactor;

import static org.eclipse.ceylon.ide.eclipse.util.EditorUtil.getEnterBinding;
import static org.eclipse.jface.text.link.ILinkedModeListener.UPDATE_CARET;
import static org.eclipse.ui.dialogs.PreferencesUtil.createPreferenceDialogOn;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener2;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.IViewportListener;
import org.eclipse.jface.text.IWidgetTokenKeeper;
import org.eclipse.jface.text.IWidgetTokenKeeperExtension;
import org.eclipse.jface.text.IWidgetTokenOwner;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tracker;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.progress.UIJob;

import org.eclipse.ceylon.ide.eclipse.code.editor.CeylonEditor;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonRefactoringPreferencePage;

public class RefactorInformationPopup 
        implements IWidgetTokenKeeper, IWidgetTokenKeeperExtension {

    public static final String LINKED_MODE_PREFERENCE_PAGE_ID = 
            "org.eclipse.ui.editors.preferencePages.LinkedModePreferencePage";

    private class PopupVisibilityManager implements 
            IPartListener2, ControlListener, MouseListener, 
            KeyListener, ITextListener, IViewportListener {

        public void start() {
            fEditor.getSite().getWorkbenchWindow().getPartService().addPartListener(this);
            final ISourceViewer viewer= fEditor.getCeylonSourceViewer();
            final StyledText textWidget= viewer.getTextWidget();
            textWidget.addControlListener(this);
            textWidget.addMouseListener(this);
            textWidget.addKeyListener(this);
            fEditor.getSite().getShell().addControlListener(this);
            viewer.addTextListener(this);
            viewer.addViewportListener(this);
            fPopup.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    fEditor.getSite().getWorkbenchWindow().getPartService().removePartListener(PopupVisibilityManager.this);
                    if (! textWidget.isDisposed()) {
                        textWidget.removeControlListener(PopupVisibilityManager.this);
                        textWidget.removeMouseListener(PopupVisibilityManager.this);
                        textWidget.removeKeyListener(PopupVisibilityManager.this);
                    }
                    fEditor.getSite().getShell().removeControlListener(PopupVisibilityManager.this);
                    viewer.removeTextListener(PopupVisibilityManager.this);
                    viewer.removeViewportListener(PopupVisibilityManager.this);
                    if (fMenuImage != null) {
                        fMenuImage.dispose();
                        fMenuImage= null;
                    }
                    if (fMenuManager != null) {
                        fMenuManager.dispose();
                        fMenuManager= null;
                    }
                    fLinkedMode.cancel();
                }
            });
        }

        public void partActivated(IWorkbenchPartReference partRef) {
            IWorkbenchPart fPart= fEditor.getEditorSite().getPart();
            if (partRef.getPart(false) == fPart) {
                updateVisibility();
            }
        }

        public void partBroughtToTop(IWorkbenchPartReference partRef) {
            // nothing to do
        }

        public void partClosed(IWorkbenchPartReference partRef) {
            // nothing to do
        }

        public void partDeactivated(IWorkbenchPartReference partRef) {
            IWorkbenchPart fPart= fEditor.getEditorSite().getPart();
            if (fPopup != null && ! fPopup.isDisposed() && partRef.getPart(false) == fPart) {
                fPopup.setVisible(false);
            }
        }

        public void partHidden(IWorkbenchPartReference partRef) {
            // nothing to do
        }

        public void partInputChanged(IWorkbenchPartReference partRef) {
            // nothing to do
        }

        public void partOpened(IWorkbenchPartReference partRef) {
            // nothing to do
        }

        public void partVisible(IWorkbenchPartReference partRef) {
            // nothing to do
        }


        public void controlMoved(ControlEvent e) {
            updatePopupLocation(true);
            updateVisibility(); //only for hiding outside editor area
        }

        public void controlResized(ControlEvent e) {
            updatePopupLocation(true);
            updateVisibility(); //only for hiding outside editor area
        }


        public void mouseDoubleClick(MouseEvent e) {
            // nothing to do
        }

        public void mouseDown(MouseEvent e) {
            // nothing to do
        }

        public void mouseUp(MouseEvent e) {
            updatePopupLocation(false);
            updateVisibility();
        }

        public void keyPressed(KeyEvent e) {
            updatePopupLocation(false);
            updateVisibility();
        }

        public void keyReleased(KeyEvent e) {
            // nothing to do
        }


        public void textChanged(TextEvent event) {
            if (!event.getViewerRedrawState())
                return;
            updatePopupLocation(false);
            updateVisibility(); //only for hiding outside editor area
        }

        public void viewportChanged(int verticalOffset) {
            updatePopupLocation(true);
            updateVisibility(); //only for hiding outside editor area
        }
    }

    /**
     * Cached platform flag for dealing with platform-specific issue:
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=219326 : Shell with custom region and SWT.NO_TRIM still has border
     */
    private static boolean MAC = Util.isMac();

    private static final int WIDGET_PRIORITY= 15;

    private static final String DIALOG_SETTINGS_SECTION= "RefactorInformationPopup";
    private static final String SNAP_POSITION_KEY= "snap_position";

    private static final int SNAP_POSITION_UNDER_RIGHT_FIELD= 0;
    private static final int SNAP_POSITION_OVER_RIGHT_FIELD= 1;
    private static final int SNAP_POSITION_UNDER_LEFT_FIELD= 2;
    private static final int SNAP_POSITION_OVER_LEFT_FIELD= 3;
    private static final int SNAP_POSITION_LOWER_RIGHT= 4;

    private static final int POPUP_VISIBILITY_DELAY= 300;

    /**
     * Offset of info hover arrow from the left or right side.
     */
    private static final int HAO= 10;

    /**
     * Width of info hover arrow.
     */
    private static final int HAW= 8;

    /**
     * Height of info hover arrow.
     */
    private static final int HAH= 10;

    /**
     * Gap between linked position and popup.
     */
    private static final int GAP= 2;

    private final CeylonEditor fEditor;
    private final AbstractLinkedMode fLinkedMode;

    private int fSnapPosition;
    private boolean fSnapPositionChanged;
    private Shell fPopup;
    private GridLayout fPopupLayout;
    private Region fRegion;

    private Image fMenuImage;
    private MenuManager fMenuManager;
    private ToolBar fToolBar;
    private boolean fIsMenuUp= false;

    private boolean fDelayJobFinished= false;

    public RefactorInformationPopup(CeylonEditor editor, 
            AbstractLinkedMode renameLinkedMode) {
        fEditor= editor;
        fLinkedMode= renameLinkedMode;
        restoreSnapPosition();
    }

    private void restoreSnapPosition() {
        IDialogSettings settings= getDialogSettings();
        try {
            fSnapPosition= settings.getInt(SNAP_POSITION_KEY);
        } catch (NumberFormatException e) {
            // default:
            fSnapPosition= SNAP_POSITION_OVER_LEFT_FIELD;
        }
        fSnapPositionChanged= true;
    }

    private IDialogSettings getDialogSettings() {
        return JavaPlugin.getDefault().getDialogSettingsSection(DIALOG_SETTINGS_SECTION);
    }

    public void open() {

        Shell workbenchShell= fEditor.getSite().getShell();
        final Display display= workbenchShell.getDisplay();

        fPopup= new Shell(workbenchShell, SWT.ON_TOP | SWT.NO_TRIM | SWT.TOOL);
        fPopupLayout= new GridLayout(2, false);
        fPopupLayout.marginWidth= 1;
        fPopupLayout.marginHeight= 1;
        fPopupLayout.marginLeft= 4;
        fPopupLayout.horizontalSpacing= 0;
        fPopup.setLayout(fPopupLayout);

        createContent(fPopup);
        updatePopupLocation(true);
        new PopupVisibilityManager().start();

        // Leave linked mode when popup loses focus
        // (except when focus goes back to workbench window or menu is open):
        fPopup.addShellListener(new ShellAdapter() {
            @Override
            public void shellDeactivated(ShellEvent e) {
                if (fIsMenuUp)
                    return;

                final Shell editorShell= fEditor.getSite().getShell();
                display.asyncExec(new Runnable() {
                    // post to UI thread since editor shell only gets activated after popup has lost focus
                    public void run() {
                        Shell activeShell= display.getActiveShell();
                        if (activeShell != editorShell) {
                            fLinkedMode.cancel();
                        }
                    }
                });
            }
        });

        if (! MAC) { // carbon and cocoa draw their own border...
            fPopup.addPaintListener(new PaintListener() {
                public void paintControl(PaintEvent pe) {
                    pe.gc.drawPolygon(getPolygon(true));
                }
            });
        }

//      fPopup.moveBelow(null); // make sure hovers are on top of the info popup
// XXX workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=170774
//      fPopup.moveBelow(workbenchShell.getShells()[0]);

        UIJob delayJob= new UIJob(display, "xxxx") {
            @Override
            public IStatus runInUIThread(IProgressMonitor monitor) {
                fDelayJobFinished= true;
                if (fPopup != null && ! fPopup.isDisposed()) {
                    updateVisibility();
                }
                return Status.OK_STATUS;
            }
        };
        delayJob.setSystem(true);
        delayJob.setPriority(Job.INTERACTIVE);
        delayJob.schedule(POPUP_VISIBILITY_DELAY);
    }

    public void close() {
        if (fPopup != null) {
            if (!fPopup.isDisposed()) {
                fPopup.close();
            }
            fPopup= null;
        }
        releaseWidgetToken();
        if (fRegion != null) {
            if (! fRegion.isDisposed()) {
                fRegion.dispose();
            }
        }
    }

    public Shell getShell() {
        return fPopup;
    }
    
    private void updatePopupLocation(boolean force) {
        fLinkedMode.updatePopupLocation();
        if (! force && fSnapPosition == SNAP_POSITION_LOWER_RIGHT)
            return;

        packPopup();
        Point loc= computePopupLocation(fSnapPosition);
        if (loc != null && ! loc.equals(fPopup.getLocation())) {
            fPopup.setLocation(loc);
            // XXX workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=170774
//          fPopup.moveBelow(fEditor.getSite().getShell().getShells()[0]);
        }
    }

    private void updateVisibility() {
        if (fPopup != null && !fPopup.isDisposed() && fDelayJobFinished) {
            boolean visible= false;
            //TODO: Check for visibility of linked position, not whether popup is outside of editor?
            if (fLinkedMode.isCaretInLinkedPosition()) {
                StyledText textWidget= fEditor.getCeylonSourceViewer().getTextWidget();
                Rectangle eArea= Geometry.toDisplay(textWidget, textWidget.getClientArea());
                Rectangle pBounds= fPopup.getBounds();
                pBounds.x-= GAP;
                pBounds.y-= GAP;
                pBounds.width+= 2 * GAP;
                pBounds.height+= 2 * GAP;
                if (eArea.intersects(pBounds)) {
                    visible= true;
                }
            }
            if (visible && ! fPopup.isVisible()) {
                /*ISourceViewer viewer= fEditor.getCeylonSourceViewer();
                if (viewer instanceof IWidgetTokenOwnerExtension) {
                    IWidgetTokenOwnerExtension widgetTokenOwnerExtension= (IWidgetTokenOwnerExtension) viewer;
                    visible= widgetTokenOwnerExtension.requestWidgetToken(this, WIDGET_PRIORITY);
                }*/
                visible = true;
            } else if (! visible && fPopup.isVisible()) {
                releaseWidgetToken();
            }
            fPopup.setVisible(visible);
        }
    }

    private void releaseWidgetToken() {
        ISourceViewer viewer= fEditor.getCeylonSourceViewer();
        if (viewer instanceof IWidgetTokenOwner) {
            IWidgetTokenOwner widgetTokenOwner= (IWidgetTokenOwner) viewer;
            widgetTokenOwner.releaseWidgetToken(this);
        }
    }

    /**
     * @param snapPosition one of the SNAP_POSITION_* constants
     * @return the location in display coordinates or <code>null</code> iff not visible
     */
    private Point computePopupLocation(int snapPosition) {
        if (fPopup == null || fPopup.isDisposed())
            return null;

        switch (snapPosition) {
            case SNAP_POSITION_LOWER_RIGHT:
            {
                StyledText eWidget= fEditor.getCeylonSourceViewer().getTextWidget();
                Rectangle eBounds= eWidget.getClientArea();
                Point eLowerRight= eWidget.toDisplay(eBounds.x + eBounds.width, eBounds.y + eBounds.height);
                Point pSize= getExtent();
                return new Point(eLowerRight.x - pSize.x - 5, eLowerRight.y - pSize.y - 5);
            }

            case SNAP_POSITION_UNDER_RIGHT_FIELD:
            case SNAP_POSITION_OVER_RIGHT_FIELD:
            {
                LinkedPosition position= fLinkedMode.getCurrentLinkedPosition();
                if (position == null)
                    return null;
                ISourceViewer viewer= fEditor.getCeylonSourceViewer();
                ITextViewerExtension5 viewer5= (ITextViewerExtension5) viewer;
                int widgetOffset= viewer5.modelOffset2WidgetOffset(position.offset + position.length);

                StyledText textWidget= viewer.getTextWidget();
                Point pos= textWidget.getLocationAtOffset(widgetOffset);
                Point pSize= getExtent();
                if (snapPosition == SNAP_POSITION_OVER_RIGHT_FIELD) {
                    pos.y-= pSize.y + GAP;
                } else {
                    pos.y+= textWidget.getLineHeight(widgetOffset) + GAP;
                }
                pos.x+= GAP;
                Point dPos= textWidget.toDisplay(pos);
                Rectangle displayBounds= textWidget.getDisplay().getClientArea();
                Rectangle dPopupRect= Geometry.createRectangle(dPos, pSize);
                Geometry.moveInside(dPopupRect, displayBounds);
                return new Point(dPopupRect.x, dPopupRect.y);
            }

            case SNAP_POSITION_UNDER_LEFT_FIELD:
            case SNAP_POSITION_OVER_LEFT_FIELD:
            default: // same as SNAP_POSITION_UNDER_LEFT_FIELD
            {
                LinkedPosition position= fLinkedMode.getCurrentLinkedPosition();
                if (position == null)
                    return null;
                ISourceViewer viewer= fEditor.getCeylonSourceViewer();
                ITextViewerExtension5 viewer5= (ITextViewerExtension5) viewer;
                int widgetOffset= viewer5.modelOffset2WidgetOffset(position.offset/* + position.length*/);

                StyledText textWidget= viewer.getTextWidget();
                Point pos= textWidget.getLocationAtOffset(widgetOffset);
                Point pSize= getExtent();
                pSize.y+= HAH + 1;
                pos.x-= HAO;
                if (snapPosition == SNAP_POSITION_OVER_LEFT_FIELD) {
                    pos.y-= pSize.y;
                } else {
                    pos.y+= textWidget.getLineHeight(widgetOffset);
                }
                Point dPos= textWidget.toDisplay(pos);
                Rectangle displayBounds= textWidget.getDisplay().getClientArea();
                Rectangle dPopupRect= Geometry.createRectangle(dPos, pSize);
                Geometry.moveInside(dPopupRect, displayBounds);
                return new Point(dPopupRect.x, dPopupRect.y);
            }

        }
    }

    private void addMoveSupport(final Shell popupShell, final Control movedControl) {
        movedControl.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseDown(final MouseEvent downEvent) {
                if (downEvent.button != 1) {
                    return;
                }

                final Point POPUP_SOURCE= popupShell.getLocation();
                final StyledText textWidget= fEditor.getCeylonSourceViewer().getTextWidget();
                Point pSize= getExtent();
                int originalSnapPosition= fSnapPosition;

                /*
                 * Feature in Tracker: it is not possible to directly control the feedback,
                 * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121300
                 * and https://bugs.eclipse.org/bugs/show_bug.cgi?id=121298#c1 .
                 *
                 * Workaround is to have an offscreen rectangle for tracking mouse movement
                 * and a manually updated rectangle for the actual drop target.
                 */
                final Tracker tracker= new Tracker(textWidget, SWT.NONE);

                final Point[] LOCATIONS= {
                        textWidget.toControl(computePopupLocation(SNAP_POSITION_UNDER_RIGHT_FIELD)),
                        textWidget.toControl(computePopupLocation(SNAP_POSITION_OVER_RIGHT_FIELD)),
                        textWidget.toControl(computePopupLocation(SNAP_POSITION_UNDER_LEFT_FIELD)),
                        textWidget.toControl(computePopupLocation(SNAP_POSITION_OVER_LEFT_FIELD)),
                        textWidget.toControl(computePopupLocation(SNAP_POSITION_LOWER_RIGHT))
                };

                final Rectangle[] DROP_TARGETS= {
                    Geometry.createRectangle(LOCATIONS[0], pSize),
                    Geometry.createRectangle(LOCATIONS[1], pSize),
                    new Rectangle(LOCATIONS[2].x, LOCATIONS[2].y + HAH, pSize.x, pSize.y),
                    Geometry.createRectangle(LOCATIONS[3], pSize),
                    Geometry.createRectangle(LOCATIONS[4], pSize)
                };
                final Rectangle MOUSE_MOVE_SOURCE= new Rectangle(1000000, 0, 0, 0);
                tracker.setRectangles(new Rectangle[] { MOUSE_MOVE_SOURCE, DROP_TARGETS[fSnapPosition] });
                tracker.setStippled(true);

                ControlListener moveListener= new ControlAdapter() {
                    /*
                     * @see org.eclipse.swt.events.ControlAdapter#controlMoved(org.eclipse.swt.events.ControlEvent)
                     */
                    @Override
                    public void controlMoved(ControlEvent moveEvent) {
                        Rectangle[] currentRects= tracker.getRectangles();
                        final Rectangle mouseMoveCurrent= currentRects[0];
                        Point popupLoc= new Point(
                                POPUP_SOURCE.x + mouseMoveCurrent.x - MOUSE_MOVE_SOURCE.x,
                                POPUP_SOURCE.y + mouseMoveCurrent.y - MOUSE_MOVE_SOURCE.y);

                        popupShell.setLocation(popupLoc);

                        Point ePopupLoc= textWidget.toControl(popupLoc);
                        int minDist= Integer.MAX_VALUE;
                        for (int snapPos= 0; snapPos < DROP_TARGETS.length; snapPos++) {
                            int dist= Geometry.distanceSquared(ePopupLoc, LOCATIONS[snapPos]);
                            if (dist < minDist) {
                                minDist= dist;
                                fSnapPosition= snapPos;
                                fSnapPositionChanged= true;
                                currentRects[1]= DROP_TARGETS[snapPos];
                            }
                        }
                        tracker.setRectangles(currentRects);
                    }
                };
                tracker.addControlListener(moveListener);
                boolean committed= tracker.open();
                tracker.close();
                tracker.dispose();
                if (committed) {
                    getDialogSettings().put(SNAP_POSITION_KEY, fSnapPosition);
                } else {
                    fSnapPosition= originalSnapPosition;
                    fSnapPositionChanged= true;
                }
                updatePopupLocation(true);
                activateEditor();
            }
        });
    }

    private void packPopup() {
        if (!fSnapPositionChanged) {
            return;
        }
        fSnapPositionChanged= false;
        
        boolean isUnderLeft= fSnapPosition == SNAP_POSITION_UNDER_LEFT_FIELD;
        boolean isOverLeft= fSnapPosition == SNAP_POSITION_OVER_LEFT_FIELD;
        fPopupLayout.marginTop= isUnderLeft ? HAH : 0;
        fPopupLayout.marginBottom= isOverLeft ? HAH + 1 : 0;
        fPopup.pack();

        Region oldRegion= fRegion;
        if (isUnderLeft || isOverLeft) {
            fRegion= new Region();
            fRegion.add(getPolygon(false));
            fPopup.setRegion(fRegion);
            Rectangle bounds= fRegion.getBounds();
            fPopup.setSize(bounds.width, bounds.height + 1);
        } else {
            fRegion= null;
            fPopup.setRegion(null);
        }

        if (oldRegion != null) {
            oldRegion.dispose();
        }
    }

    private Point getExtent() {
        Point e = fPopup.getSize();
        switch (fSnapPosition) {
            case SNAP_POSITION_UNDER_LEFT_FIELD:
                e.y -= HAH;
                break;
            case SNAP_POSITION_OVER_LEFT_FIELD:
                e.y -= HAH + 1;
                break;
        }
        return e;
    }

    private int[] getPolygon(boolean border) {
        Point e = getExtent();
        int b = border ? 1 : 0;
        boolean isRTL= (fPopup.getStyle() & SWT.RIGHT_TO_LEFT) != 0;
        int ha1= isRTL ? e.x - HAO :           HAO + HAW;
        int ha2= isRTL ? e.x - HAO - HAW / 2 : HAO + HAW / 2;
        int ha3= isRTL ? e.x - HAO - HAW :     HAO;
            int[] poly;
            switch (fSnapPosition) {
                case SNAP_POSITION_OVER_LEFT_FIELD:
                    poly= new int[] {
                            0, 0,
                            e.x - b, 0,
                            e.x - b, e.y - b,
                            ha1, e.y - b,
                            ha2, e.y + HAH - b,
                            ha3, e.y - b,
                            0, e.y - b,
                            0, 0 };
                    break;

                case SNAP_POSITION_UNDER_LEFT_FIELD:
                    poly= new int[] {
                            0, HAH,
                            ha3 + b, HAH,
                            ha2, b,
                            ha1 - b, HAH,
                            e.x - b, HAH,
                            e.x - b, e.y + HAH - b,
                            0, e.y + HAH - b,
                            0, HAH };
                    break;

                default:
                    poly= new int[] {
                            0, 0,
                            e.x - b, 0,
                            e.x - b, e.y - b,
                            0, e.y - b,
                            0, 0 };
                    break;
            }
        return poly;
    }

    private StyledText hint;
    
    private void createContent(Composite parent) {
        Display display= parent.getDisplay();
        Color foreground= display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
        Color background= display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
        addMoveSupport(fPopup, parent);

        hint= new StyledText(fPopup, SWT.READ_ONLY | SWT.SINGLE);
        hint.setForeground(foreground);
        setHintTemplate(fLinkedMode.getHintTemplate());
        hint.setEnabled(false); // text must not be selectable
        addMoveSupport(fPopup, hint);

        addViewMenu(parent);

        recursiveSetBackgroundColor(parent, background);

    }

    public void setHintTemplate(String hintTemplate) {
        String enterKeyName = " \u22ef " + getEnterBinding();
        hint.setText(hintTemplate.replace("{0}", enterKeyName));
        hint.setStyleRange(new StyleRange(hintTemplate.indexOf("{0}"), 
                enterKeyName.length(), null, null, SWT.BOLD));
    }

    private ToolBar addViewMenu(final Composite parent) {
        fToolBar= new ToolBar(parent, SWT.FLAT);
        final ToolItem menuButton = new ToolItem(fToolBar, SWT.PUSH, 0);
        fMenuImage= JavaPluginImages.DESC_ELCL_VIEW_MENU.createImage();
        menuButton.setImage(fMenuImage);
        menuButton.setToolTipText("Menu");
        fToolBar.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                showMenu(fToolBar);
            }
        });
        menuButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                showMenu(fToolBar);
            }
        });
        fToolBar.pack();
        return fToolBar;
    }

    private void showMenu(ToolBar toolBar) {
        Menu menu= getMenuManager().createContextMenu(toolBar);
        menu.setLocation(toolBar.toDisplay(0, toolBar.getSize().y));
        fIsMenuUp= true;
        menu.setVisible(true);
    }

    MenuManager getMenuManager() {
        if (fMenuManager != null) {
            return fMenuManager;
        }

        fMenuManager= new MenuManager();
        fMenuManager.setRemoveAllWhenShown(true);

        fMenuManager.addMenuListener(new IMenuListener2() {
            public void menuAboutToHide(IMenuManager manager) {
                fIsMenuUp = false;
            }
            public void menuAboutToShow(IMenuManager manager) {                
                addApplyMenuItem(manager);
                fLinkedMode.addAdditionalMenuItems(manager);
                manager.add(new Separator());
                addSnapToMenuItem(manager);
                addPreferencesMenuItem(manager);
            }
        });
        
        return fMenuManager;
    }

    private void addPreferencesMenuItem(IMenuManager manager) {
        IAction prefsAction= new Action("Preferences...") {
            @Override
            public void run() {
                fLinkedMode.cancel();
                Shell shell = fEditor.getSite().getShell();
                createPreferenceDialogOn(shell, 
                        CeylonRefactoringPreferencePage.ID, 
                        new String[] { 
                                LINKED_MODE_PREFERENCE_PAGE_ID, 
                                CeylonRefactoringPreferencePage.ID 
                        }, null).open();
            }
        };
        manager.add(prefsAction);
    }
    
    private void addApplyMenuItem(IMenuManager manager) {
        IAction refactorAction= new Action("Apply") {
            @Override
            public void run() {
                fLinkedMode.linkedModeModel.exit(UPDATE_CARET);
//                fRenameLinkedMode.done();
            }
        };
        refactorAction.setAccelerator(SWT.CR);
        refactorAction.setEnabled(true);
        manager.add(refactorAction);
    }
    
    private void addSnapToMenuItem(IMenuManager manager) {
        MenuManager subMenuManager= new MenuManager("Snap To");
        addMoveMenuItem(subMenuManager, SNAP_POSITION_UNDER_LEFT_FIELD, "Snap Under Left");
        addMoveMenuItem(subMenuManager, SNAP_POSITION_UNDER_RIGHT_FIELD, "Snap Under Right");
        addMoveMenuItem(subMenuManager, SNAP_POSITION_OVER_LEFT_FIELD, "Snap Over Left");
        addMoveMenuItem(subMenuManager, SNAP_POSITION_OVER_RIGHT_FIELD, "Snap Over Right");
        addMoveMenuItem(subMenuManager, SNAP_POSITION_LOWER_RIGHT, "Snap Bottom Right");
        manager.add(subMenuManager);
    }
    
    private void addMoveMenuItem(IMenuManager manager, final int snapPosition, String text) {
        IAction action= new Action(text, IAction.AS_RADIO_BUTTON) {
            @Override
            public void run() {
                fSnapPosition= snapPosition;
                fSnapPositionChanged= true;
                getDialogSettings().put(SNAP_POSITION_KEY, fSnapPosition);
                updatePopupLocation(true);
                activateEditor();
            }
        };
        action.setChecked(fSnapPosition == snapPosition);
        manager.add(action);
    }

    private static void recursiveSetBackgroundColor(Control control, Color color) {
        control.setBackground(color);
        if (control instanceof Composite) {
            Control[] children= ((Composite) control).getChildren();
            for (int i= 0; i < children.length; i++) {
                recursiveSetBackgroundColor(children[i], color);
            }
        }
    }

    public boolean ownsFocusShell() {
        if (fIsMenuUp)
            return true;
        if (fPopup == null || fPopup.isDisposed())
            return false;
        Shell activeShell= fPopup.getDisplay().getActiveShell();
        if (fPopup == activeShell)
            return true;
        return false;
    }

    private void activateEditor() {
        fEditor.getSite().getPage().activate(fEditor);
    }

    public boolean requestWidgetToken(IWidgetTokenOwner owner) {
        return true;
    }

    public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) {
        if (priority > WIDGET_PRIORITY) {
            if (fPopup != null && !fPopup.isDisposed()) {
                fPopup.setVisible(false);
            }
            return true;
        }
        return false;
    }

    public boolean setFocus(IWidgetTokenOwner owner) {
        if (fToolBar != null && ! fToolBar.isDisposed())
            showMenu(fToolBar);
        return true;
    }
}
